﻿using Abp.Application.Features;
using Abp.AspNetCore.Mvc.Extensions;
using Abp.Dependency;
using Abp.Logging;
using Abp.Runtime.Session;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
using Mt.Site.Core.Features;
using Mt.Site.Core.StaticPages;
using Mt.Site.Core.Url;
using Mt.Site.Core.Web.Rewriters;
using System;
using System.IO;
using System.Threading.Tasks;

namespace Mt.Site.Web.Filters
{
    public class StaticPageFilterAttribute : ITransientDependency, IAsyncResultFilter
    {
        public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
        {
            var checkResult = await ValidateActionConfiguration(context);
            if(checkResult != null && checkResult.SavePath != null)
            {
                var response = context.HttpContext.Response;
                if (!response.Body.CanRead || !response.Body.CanSeek)
                {
                    using (var ms = new MemoryStream())
                    {
                        var old = response.Body;
                        response.Body = ms;

                        await next();

                        var hasError = context.HttpContext.Response.StatusCode != 200;
                        if (!hasError)
                        {
                            var staticPageFeature = context.HttpContext.Features.Get<IStaticPageFeature>();
                            hasError = staticPageFeature.Disable;    
                        }
                        if (!hasError)
                        {
                            await SaveHtmlResult(response.Body, checkResult);
                        }

                        ms.Position = 0;
                        await ms.CopyToAsync(old);
                        response.Body = old;
                    }
                }
                else
                {
                    await next();
                    var old = response.Body.Position;

                    var hasError = context.HttpContext.Response.StatusCode != 200;
                    if (!hasError)
                    {
                        var staticPageFeature = context.HttpContext.Features.Get<IStaticPageFeature>();
                        hasError = staticPageFeature.Disable;
                    }
                    if (!hasError)
                    {
                        await SaveHtmlResult(response.Body, checkResult);
                    }

                    response.Body.Position = old;
                }
            }
            else
            {
                await next();
            }
        }

        private async Task<ValidateActionConfigResult> ValidateActionConfiguration(ResultExecutingContext context)
        {
            if (!context.ActionDescriptor.IsControllerAction())
            {
                return null;
            }

            var staticPageFeature = context.HttpContext.Features.Get<IStaticPageFeature>();
            string savePath = staticPageFeature?.StaticPageSavePath;

            if (savePath != null)
            {
                var staticPageSettingManager = context.HttpContext.RequestServices.GetRequiredService<IStaticPageSettingManager>();
                var url = UrlChecker.GetAbsolutePath(context.HttpContext.Request);
                var isNeedStaticPage = staticPageSettingManager.IsNeedStaticPage(url);

                if (!isNeedStaticPage)
                {
                    return null;
                }

                var abpSession = context.HttpContext.RequestServices.GetRequiredService<IAbpSession>();
                if (abpSession.TenantId == null)
                {
                    return null;
                }

                var featureChecker = context.HttpContext.RequestServices.GetRequiredService<IFeatureChecker>();
                if (!await featureChecker.IsEnabledAsync(AppFeatures.StaticPage))
                {
                    return null;
                }

                var _context = context.HttpContext;
                var request = _context.Request;

                DeviceType deviceType = UrlChecker.IsMobile(_context) ? DeviceType.Moblie : DeviceType.Pc;
                var authority = UrlChecker.GetAuthority(request);
                var absolutePath = UrlChecker.GetAbsolutePath(request);

                return new ValidateActionConfigResult
                {
                    SavePath = savePath,
                    RawUrl = staticPageFeature?.RawUrl,
                    DeviceType = deviceType,
                    Authority = authority,
                    AbsolutePath = absolutePath
                };
            }
            return null;
        }

        private async Task SaveHtmlResult(Stream stream, ValidateActionConfigResult staticPageContext)
        {
            if (!File.Exists(staticPageContext.SavePath))
            {
                await SaveAsync(staticPageContext.SavePath, stream, staticPageContext.AbsolutePath, staticPageContext.RawUrl, staticPageContext.DeviceType, staticPageContext.Authority);
            }
        }

        private async Task SaveAsync(string path, Stream ms,
            string realUrl, string visitUrl,
            DeviceType deviceType, string authority)
        {
            var dir = Path.GetDirectoryName(path);

            if (!Directory.Exists(dir))
                Directory.CreateDirectory(dir);

            try
            {
                ms.Seek(0, SeekOrigin.Begin);
                using (var fs = new FileStream(
                    path: path,
                    mode: FileMode.Create,
                    access: FileAccess.Write,
                    share: FileShare.None,
                    bufferSize: 4096,
                    useAsync: true))
                {
                    await ms.CopyToAsync(fs);
                }

                //更新记录
                using (var staticPageManager = IocManager.Instance.ResolveAsDisposable<IStaticPageManager>())
                {
                    await staticPageManager.Object.GenerateSuccessful(realUrl, visitUrl, deviceType, path, authority);
                }
            }
            catch (Exception ex)
            {
                LogHelper.Logger.Error($"realUrl:{realUrl},visitUrl:{visitUrl},deviceType:{deviceType},host:{authority}");
                LogHelper.LogException(ex);
            }
        }
    }

    internal class ValidateActionConfigResult
    {
        public string SavePath { get; set; }

        public string RawUrl { get; set; }

        public DeviceType DeviceType { get; set; }

        public string Authority { get; set; }

        public string AbsolutePath { get; set; }
    }
}
